package com.sj.purchase.modules.cost.service.price.impl;

import com.alibaba.fastjson.JSON;
import com.sj.purchase.modules.cost.bo.CostPriceBO;
import com.sj.purchase.modules.cost.domain.order.InOrder;
import com.sj.purchase.modules.cost.domain.order.Order;
import com.sj.purchase.modules.cost.domain.price.CostPrice;
import com.sj.purchase.modules.cost.repository.order.InOrderRepository;
import com.sj.purchase.modules.cost.repository.order.StockAdjustRepository;
import com.sj.purchase.modules.cost.service.price.CostPriceService;
import com.sj.purchase.modules.cost.repository.order.OutOrderRepository;
import com.sj.purchase.modules.cost.repository.price.CostPriceRepository;
import com.sj.purchase.modules.cost.domain.order.AdjustOrder;
import com.sj.purchase.modules.cost.domain.order.OutOrder;
import com.sj.purchase.modules.cost.utils.DoubleUtil;
import com.sj.purchase.util.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Created by yangrd on 2017/10/26
 **/
@Service
public class CostPriceServiceImpl implements CostPriceService {

    private static final Logger LOGGER = LoggerFactory.getLogger(CostPriceServiceImpl.class);

    @Autowired
    private CostPriceRepository repository;

    @Autowired
    private InOrderRepository inOrderRepository;

    @Autowired
    private OutOrderRepository outOrderRepository;

    @Autowired
    private StockAdjustRepository adjustRepository;

    @Override
    public Double getCostPrice(String skuNo, Date date) {
        CostPrice costPrice = repository.findFirstBySkuNoAndCreateDateBeforeOrderByCreateDateDesc(skuNo, date);
        if (Objects.isNull(costPrice)) {
            LOGGER.debug("sku:{} date:{} 没有找到 返回默认值0", skuNo, date);
            return 0D;
        } else {
            return DoubleUtil.format(costPrice.getAvgCostPrice());
        }
    }

    @Override
    public List<CostPrice> listCostPrice(Date start, Date end) {
        return repository.findByCreateDateBetweenOrderByCreateDateDesc(start, end, null).getContent();
    }


    @Override
    public Map<String, List<CostPrice>> getSkuCostPriceCache(List<CostPrice> costPriceList) {
        Map<String, List<CostPrice>> sortMap = costPriceList.stream().collect(Collectors.groupingBy(CostPrice::getSkuNo));
        sortMap.forEach((_k, v) ->
                v.sort((o1, o2) -> {
                    Long n = o2.getCreateDate().getTime() - o1.getCreateDate().getTime();
                    return n > 0 ? 1 : (n < 0 ? -1 : 0);
                })
        );
        return sortMap;
    }

    @Override
    public Double getCostPrice(Map<String, List<CostPrice>> skuCostPriceCache, String skuNo, Date date) {
        if (skuCostPriceCache.containsKey(skuNo)) {
            Double avgCostPrice = skuCostPriceCache.get(skuNo).stream().filter(costPrice -> costPrice.getCreateDate().getTime() < date.getTime()).findFirst().orElseGet(CostPrice::new).getAvgCostPrice();
            if (Objects.nonNull(avgCostPrice)) {
                return avgCostPrice;
            }

        }
        LOGGER.info("sku:{} date:{} 移动平均成本价緩存沒有命中 去數據庫中再次查找", skuNo, date);
        return getCostPrice(skuNo, date);
    }


    /**
     * |出入库|出入库方式|库存更改|是否计算平均价
     * 入    采购       +       o
     * 入   销售退货     +       o
     * 出   采购退货     -       o
     * 出   销售        -       x
     * 出   其他        -       x
     * -    库存调整    +-       x
     */
    @Override
    public void calculateTheCostPrice(Date date) {
        //计算昨天的移动成本价 需要等出库入库调整单同步完成之后进行
        Date start = DateUtils.getStartOfDay(date);
        Date end = DateUtils.addDays(start, 1);
        calculateTheCostPrice(start, end);
    }


    @Transactional
    public void calculateTheCostPrice(Date start, Date end) {
        List<OutOrder> outOrderList = outOrderRepository.findByCreateDateBetweenOrderByCreateDate(start, end);
        List<InOrder> inOrderList = inOrderRepository.findByCreateDateBetweenOrderByCreateDate(start, end);
        List<AdjustOrder> adjustOrderList = adjustRepository.findByCreateDateBetween(start, end);
        List<CostPrice> list = listCostPrice(outOrderList, inOrderList, adjustOrderList);
        LOGGER.debug("{}", JSON.toJSONString(list));
        repository.save(list);
    }

    private List<CostPrice> listCostPrice(List<OutOrder> outOrderList, List<InOrder> inOrderList, List<AdjustOrder> adjustOrderList) {
        //初值
        Map<String, Long> qtyMap = new HashMap<>();//期初時的數量
        Map<String, Double> avgPriceMap = new HashMap<>();
        List<CostPriceBO> costPricePOList = repository.findCostPricePOByMaxDate();
        costPricePOList.forEach(costPricePO -> {
            qtyMap.put(costPricePO.getSkuNo(), costPricePO.getInventoryQty());
            avgPriceMap.put(costPricePO.getSkuNo(), costPricePO.getAvgCostPrice());
        });

        SimpleCostPriceManager.init(outOrderList, inOrderList, adjustOrderList);
        List<CostPrice> costPriceList = SimpleCostPriceManager.stream().
                filter(SimpleCostPrice::getChangeAvgCostPrice).
                map(simpleCostPrice -> {
                    String skuNo = simpleCostPrice.getSkuNo();

                    //该sku getDate()之前的所有明细
                    List<SimpleCostPrice> simpleCostPriceList = SimpleCostPriceManager.listSimpleCostPriceByDateBefore(skuNo, simpleCostPrice.getDate());

                    Long qty1 = qtyMap.getOrDefault(skuNo, 0L) + simpleCostPriceList.stream().mapToLong(SimpleCostPrice::getQty).sum();
                    Double avgPrice1 = avgPriceMap.getOrDefault(skuNo, 0D);
                    Long qty2 = simpleCostPrice.getQty();
                    Double price = simpleCostPrice.getPrice();
                    //计算平均价
                    Double avgPrice2 = calculateAveragePrice(qty1, avgPrice1, qty2, price);
                    avgPriceMap.put(skuNo, avgPrice2);
                    String orderNo = simpleCostPrice.getOrderNo();
                    Order.OrderType orderType = simpleCostPrice.getOrderType();
                    Date date = simpleCostPrice.getDate();
                    return new CostPrice(orderNo, orderType, skuNo, date, avgPrice1, qty1, avgPrice2, qty1 + qty2);
                }).collect(Collectors.toList());
        SimpleCostPriceManager.destroy();
        // sku date num price 平均价是否更改 （按时间排序遇到计算平均价的就计算一下之前库存）
        return costPriceList;
    }


    /**
     * 计算平均成本价
     *
     * @param qty1      当前数量
     * @param avgPrice1 当前平均成本
     * @param qty2      入库数量
     * @param price2    入库成本
     * @return 入库之后的平均成本
     */
    private Double calculateAveragePrice(Long qty1, Double avgPrice1, Long qty2, Double price2) {
        if (qty1 == null || avgPrice1 == null || qty2 == null || price2 == null) {
            throw new NullPointerException();
        }
        if (qty1 < 0) {
            return price2;
        }
        if (qty1 + qty2 <= 0) {
            return 0D;
        } else {
            Double avgPrice2 = (qty1 * avgPrice1 + qty2 * price2) / (qty1 + qty2);
            LOGGER.debug("[计算平均成本价] :=> qty1:{}, avgPrice1:{}, qty2:{}, price2:{} = avgPrice2:{}", qty1, avgPrice1, qty2, price2, avgPrice2);
            return avgPrice2;
        }
    }


    private static class SimpleCostPriceManager {

        private static ThreadLocal<List<SimpleCostPrice>> threadLocal = new ThreadLocal<>();
        //用于查询
        private static ThreadLocal<Map<String, List<SimpleCostPrice>>> listThreadLocal = new ThreadLocal<>();

        public static void init(List<OutOrder> outOrderList, List<InOrder> inOrderList, List<AdjustOrder> adjustOrderList) {
            List<SimpleCostPrice> simpleCostPriceList = new ArrayList<>();
            simpleCostPriceList.addAll(listSimpleCostPriceByOutOrder(outOrderList));
            simpleCostPriceList.addAll(listSimpleCostPriceByInOrder(inOrderList));
            simpleCostPriceList.addAll(listSimpleCostPriceByAdjustOrder(adjustOrderList));
            simpleCostPriceList.sort((o1, o2) -> {
                Long c = o1.getDate().getTime() - o2.getDate().getTime();
                return c > 0 ? 1 : (c < 0 ? -1 : 0);
            });
            Map<String, List<SimpleCostPrice>> skuListMap = simpleCostPriceList.stream().collect(Collectors.groupingBy(SimpleCostPrice::getSkuNo));
            set(simpleCostPriceList);
            setSimpleCostPriceMap(skuListMap);
        }

        public static Stream<SimpleCostPrice> stream() {
            List<SimpleCostPrice> simpleCostPriceList = get();
            return simpleCostPriceList.stream();
        }


        public static List<SimpleCostPrice> listSimpleCostPriceByDateBefore(String skuNo, Date date) {
            List<SimpleCostPrice> simpleCostPriceList = listSimpleCostPrice(skuNo);
            return simpleCostPriceList.stream().filter(simpleCostPrice -> {
                Long time = simpleCostPrice.getDate().getTime();
                return time < date.getTime();
            }).collect(Collectors.toList());
        }

        public static void destroy() {
            set(null);
            setSimpleCostPriceMap(null);
        }

        private static List<SimpleCostPrice> get() {
            return threadLocal.get();
        }

        private static void set(List<SimpleCostPrice> list) {
            threadLocal.set(list);
        }


        private static List<SimpleCostPrice> listSimpleCostPrice(String skuNo) {
            return listThreadLocal.get().get(skuNo);
        }

        private static void setSimpleCostPriceMap(Map<String, List<SimpleCostPrice>> skuCostPriceMap) {
            listThreadLocal.set(skuCostPriceMap);
        }

        private static List<SimpleCostPrice> listSimpleCostPriceByOutOrder(List<OutOrder> outOrderList) {
            return outOrderList.stream().map(OutOrder::getOrderItems).flatMap(Set::stream).map(outOrderItem ->
                    {
                        String orderNo = outOrderItem.getOrder().getOrderNo();
                        OutOrder.OrderType orderType = outOrderItem.getOrder().getType();
                        String skuNo = outOrderItem.getSkuNo();
                        Date date = outOrderItem.getOrder().getCreateDate();
                        Long qty = outOrderItem.getNum() * -1;
                        Double price = outOrderItem.getPrice();
                        Boolean isChangeAvgCostPrice = false;
                        if (orderType.equals(OutOrder.OrderType.OUT_PROCUREMENT_RETURNS)) {
                            isChangeAvgCostPrice = true;
                        }
                        return SimpleCostPrice.of(orderNo, orderType, skuNo, date, qty, price, isChangeAvgCostPrice);
                    }
            ).collect(Collectors.toList());
        }


        private static List<SimpleCostPrice> listSimpleCostPriceByInOrder(List<InOrder> inOrderList) {
            return inOrderList.stream().map(InOrder::getOrderItems).flatMap(Set::stream).map(inOrderItem ->
                    {
                        String orderNo = inOrderItem.getOrder().getOrderNo();
                        Order.OrderType orderType = inOrderItem.getOrder().getType();
                        String skuNo = inOrderItem.getSkuNo();
                        Date date = inOrderItem.getOrder().getCreateDate();
                        Long qty = inOrderItem.getNum();
                        Double price = inOrderItem.getPrice();
                        return SimpleCostPrice.of(orderNo, orderType, skuNo, date, qty, price, true);
                    }
            ).collect(Collectors.toList());
        }

        private static List<SimpleCostPrice> listSimpleCostPriceByAdjustOrder(List<AdjustOrder> adjustOrderList) {
            return adjustOrderList.stream().map(adjustOrder ->
                    {
                        String orderNo = adjustOrder.getAdjustmentNo();
                        String skuNo = adjustOrder.getSkuNo();
                        Date date = adjustOrder.getCreateDate();
                        Long qty = adjustOrder.getAdjustedQty().longValue();
                        Double price = 0D;
                        return SimpleCostPrice.of(orderNo, null, skuNo, date, qty, price, false);
                    }
            ).collect(Collectors.toList());
        }
    }

    private static class SimpleCostPrice implements Comparable<SimpleCostPrice> {
        private String orderNo;
        private Order.OrderType orderType;
        private String skuNo;
        private Date date;
        private Long qty;
        private Double price;
        private Boolean isChangeAvgCostPrice;

        public static SimpleCostPrice of(String orderNo, Order.OrderType orderType, String skuNo, Date date, Long qty, Double price, Boolean isChangeAvgCostPrice) {
            return new SimpleCostPrice(orderNo, orderType, skuNo, date, qty, price, isChangeAvgCostPrice);
        }

        public SimpleCostPrice(String orderNo, Order.OrderType orderType, String skuNo, Date date, Long qty, Double price, Boolean isChangeAvgCostPrice) {
            this.orderNo = orderNo;
            this.orderType = orderType;
            this.skuNo = skuNo;
            this.date = date;
            this.qty = qty;
            this.price = price;
            this.isChangeAvgCostPrice = isChangeAvgCostPrice;
        }

        public String getOrderNo() {
            return orderNo;
        }

        public Order.OrderType getOrderType() {
            return orderType;
        }

        public String getSkuNo() {
            return skuNo;
        }

        public Date getDate() {
            return date;
        }

        public Long getQty() {
            return qty;
        }

        public Double getPrice() {
            return price;
        }

        public Boolean getChangeAvgCostPrice() {
            return isChangeAvgCostPrice;
        }

        @Override
        public int compareTo(SimpleCostPrice o) {
            Long c = getDate().getTime() - o.getDate().getTime();
            return c > 0 ? 1 : (c < 0 ? -1 : 0);
        }
    }

}
